
#include "task.h"
#include "parser.h"
#include "logger.hpp"

int Task::maxIndex = 0;

const static std::string DELIMITER ="  ";
const static std::string TASK_DONE = "true";
const static std::string TASK_NOTDONE = "false";
const static std::string TASK_DONE_COLOR = "blue";
const static std::string TASK_OVERDUE_COLOR = "#CC3300";
const static std::string TASK_ONGOING_COLOR = "#006B24";
const static std::string CATEGORY_COLOR = "#4C4C4C";
const static std::string DATE_COLOR = "black";

	
Task::Task() {
	std::swap(*this, Task(true));
	setUndone();
}

Task::Task(bool valid) {
	done = false;
	this->valid = valid;
	maxIndex ++;
	this->id = maxIndex;
}

//@author  A0105541U
bool Task::fromString (string st) {
	int lenDelimiter = DELIMITER.length();
	std::size_t posDelimiter = st.find(DELIMITER);
	std::size_t posNextDelimiter;
	if (posDelimiter == std::string::npos) {
		// no complete/incomplete information
		valid = false;
		return false;
	}
	string done = st.substr(0, posDelimiter);
	if (done == TASK_DONE) {
		this->done = true;
	} else if (done == TASK_NOTDONE) {
		this->done = false;
	} else {
		valid = false;
		return false;
	}
	
	posNextDelimiter = st.find(DELIMITER, posDelimiter+2);
	if (posNextDelimiter == std::string::npos) {
		// no task name
		valid = false;
		return false;
	}

	setTaskName(st.substr(posDelimiter + lenDelimiter, posNextDelimiter-posDelimiter-lenDelimiter));
	posDelimiter = posNextDelimiter;

	std::size_t posCategory = st.find("  #");
	while (posCategory != std::string::npos) {
			posDelimiter = st.find(DELIMITER, posDelimiter+lenDelimiter);
		    addCategory(st.substr(posCategory+3, posDelimiter - posCategory-3));
			posCategory = st.find("  #", posCategory+1);
	}
  
	posNextDelimiter = st.find(DELIMITER, posDelimiter+2);
	
	if (posNextDelimiter == std::string::npos)  {
		if (posDelimiter == st.length()-lenDelimiter) {
			setFloating(); 
		} else {
			valid = false;
			return false;
		}
	} else {
		Date firstDate;
		firstDate.fromString(st.substr(posDelimiter+lenDelimiter, posNextDelimiter-posDelimiter-lenDelimiter));
		if (firstDate.isValid() == false) {
			valid = false;
			return false;
		}
		if (posNextDelimiter == st.length()-lenDelimiter) {
				setDeadline(firstDate); 
		} else {
			posDelimiter = posNextDelimiter;
			posNextDelimiter = st.find(DELIMITER, posDelimiter+lenDelimiter);
			Date secondDate;
			secondDate.fromString(st.substr(posDelimiter+lenDelimiter, posNextDelimiter-posDelimiter-lenDelimiter));
			if (secondDate.isValid() == false || posNextDelimiter == std::string::npos) {
				valid = false;
				return false;
			}
			setStartEnd(firstDate, secondDate);
		}
		
	}

	this->valid = true;
	return true;
}

//@author A0105541U
Task::Task(string st) {
	fromString(st);
	maxIndex ++;
	this->id = maxIndex;
}
//@author A0096566M
void Task::setTaskName(string givenTaskName) {
	taskName = givenTaskName;
}
//@author A0096566M
void Task::setStartEnd(Date givenStartTime, Date givenEndTime) {
	assert (givenStartTime.isValid() == true && givenEndTime.isValid() == true);

    if(givenStartTime>givenEndTime){
        end = givenStartTime;
        start = givenEndTime;
    } else {
        start = givenStartTime;
        end = givenEndTime;
    }
	taskType = SCHEDULE;
}

// @Author A0105541U
void Task::setCategories(vector<string> givenCategories) {
	categories = givenCategories;
}

// @Author A0105541U
bool Task::haveCategory(string category) {
	vector<string>::iterator it;
	for(it = categories.begin(); it != categories.end(); it++) {
		if ((*it).compare(category)==0) return true;
	}
	return false;
}

// @Author A0105541U
bool Task::isValid() {
	return this->valid;
}

// @Author A0105541U
int Task::getId() {
	return this->id;
}

// @Author A0105541U
void Task::addCategory (string category) {
	if (this->haveCategory(category) == false) {
		categories.push_back(category);
	}
}

// @Author A0105541U
void Task::setDeadline(Date givenDeadline) {
	end = givenDeadline;
	taskType = DEADLINE;
};

// @Author A0105541U
void Task::setFloating() {
	taskType = FLOATING;
};

// @Author A0105541U
void Task::setDone() {
	done = true;
};

// @Author A0105541U
void Task::setUndone() {
	done = false;
};

//@author A0101728M
bool Task::isDone(){
	return done;
}

bool Task::isBefore(Date date){
	if (taskType == FLOATING){
		return false;
	}
	else if (taskType == SCHEDULE){
		return this->getStart() < date;
	}
	else {
		//Deadline
		return this->getEnd() < date;
	}
}

bool Task::isAfter(Date date){
	if (taskType == FLOATING){
		return true;
	}
	else {
		return end > date;
	}
}

bool Task::isBetween(Date dateStart, Date dateEnd){
	if (dateEnd < dateStart){
		Date temp = Date(dateStart);
		dateStart = dateEnd;
		dateEnd = temp;
	}
	return isBefore(dateEnd) && isAfter(dateStart);
}

bool Task::isOver(){
	if (taskType == FLOATING){
		return false;
	}
	else {
		return (this->getEnd() < Date::now()) && done == true;
	}
}

bool Task::isOverdue(){
	if (taskType == FLOATING){
		return false;
	}
	else {
		return (this->getEnd() < Date::now()) && done == false;
	}
}

bool Task::isUpcoming(){
	return isAfter(Date::now());
}

bool Task::containsSubsequence(string sourceStr, string targetStr){
	char* source = new char[sourceStr.length() + 1];
	char* target = new char[targetStr.length() + 1];
	memcpy(source, boost::algorithm::to_lower_copy(sourceStr).c_str(), sourceStr.length() + 1);
	memcpy(target, boost::algorithm::to_lower_copy(targetStr).c_str(), targetStr.length() + 1);

	int i = 0;
	int j = 0;
	int count = 0;

	while (i < sourceStr.length() && j < targetStr.length()) {
		if (source[i] == target[j]){
			i++;
			j++;
			count++;
		}
		else {
			i++;
		}
	}

	return count == targetStr.length();
}

//returns true if less than half the target string needs to be changed to become the taskname
bool Task::nameContainsFuzzy(string target, int threshold){
	float editDist = float(Parser::editDistance(target, taskName));
	log(LOG_DEBUG) << "Task::nameContainsFuzzy - " << "edit distance: " << editDist;
	log(LOG_DEBUG) << "Task::nameContainsFuzzy - " << "target length: " << target.length();
	float changed = editDist / float(target.length()) * 100; //scale of 0-100
	log(LOG_DEBUG) << "Task::nameContainsFuzzy - " << taskName << " has difference of " << changed << "%";
	return changed < threshold;
}

bool Task::nameContainsSubsequence(string subsq){
	return containsSubsequence(getTaskName(), subsq);
}

bool Task::categoriesContainSubsequence(string subsq){
	vector<string> categories = getCategories();
	for (std::vector<string>::iterator itr = categories.begin(); itr != categories.end();) {
		if (categoryContainsSubsequence(*itr, subsq)){
			return true;
		}
		else {
			itr++;
		}
	}
	return false;
}

 bool Task::categoryContainsSubsequence(string category, string subsq){
	 return containsSubsequence(category, subsq);
}

// @Author A0105541U
string Task::getTaskName() {
	return this->taskName;
}

// @Author A0105541U
vector<string> Task::getCategories(){
	return categories;
}

// @Author A0105541U
Date Task::getStart() {
	if (taskType != SCHEDULE) throw "invalid getstart";
	assert(taskType == SCHEDULE);
	return this->start;
}

// @Author A0105541U
Date Task::getEnd() {
	if (taskType == FLOATING) throw "invalid getstart";
	assert(taskType != FLOATING);
	return this->end;
}

// @Author A0105541U
TaskType Task::getTaskType() {
	return this->taskType;
}

// @Author A0105541U
string Task::toScreenString() {
	return this->taskName;
}

// @Author A0105541U
string setTabledHTMLColor(string s, string color){
    char buffer[255];
	sprintf_s(buffer, "<td style=\"color:%s\">%s</td>", color.c_str(),s.c_str());
	return buffer;
}

//@author A0096566M
string setHTMLColor(string s, string color){
    string prepend = string("<span style=\"color: ")+color+string(";\">");
	string append = "</span>";
	return prepend+s+append;
}

// @author A0105541U
string Task::toHTMLString() {
	string result="", date1, date2;
	if(this->isDone()){
		result += setHTMLColor(this->getTaskName(),TASK_DONE_COLOR);
		result += " ";
	}else if (this->isOverdue()) {
		result += setHTMLColor(this->getTaskName(),TASK_OVERDUE_COLOR);
		result += " ";
	} else {
		result += setHTMLColor(this->getTaskName(),TASK_ONGOING_COLOR);
		result += " ";
	}

	if (this->taskType == TaskType::SCHEDULE) {
		date1 = setHTMLColor(this->getStart().toDisplayString(),DATE_COLOR);
		date2 = setHTMLColor(this->getEnd().toDisplayString(), DATE_COLOR);
		date1 += " " + date2;
		result += date1;
		result += " ";
	} else if (this->taskType == TaskType::DEADLINE) {
		date1 = setHTMLColor(this->getEnd().toDisplayString(), DATE_COLOR);
		result += date1;
		result += " ";
	} else {
	}

	string c = "";
	for (int i=0; i<categories.size(); i++) {
		c += "#";
		c +=categories[i];
		c +=" ";
	}
	result += setHTMLColor(c, CATEGORY_COLOR);
	return result;
}

// @Author A0105541U
string Task::toTabledHTMLString() {
	if (!isValid()) throw "invalid toTableString";
	assert(isValid());
	string result="", date1, date2;
	if(this->isDone()){
		result += setTabledHTMLColor(this->getTaskName(), TASK_DONE_COLOR);
	}else if (this->isOverdue()) {
		result += setTabledHTMLColor(this->getTaskName(),TASK_OVERDUE_COLOR);
	} else {
		result += setTabledHTMLColor(this->getTaskName(), TASK_ONGOING_COLOR);
	}

	if (this->taskType == TaskType::SCHEDULE) {
		date1 = setTabledHTMLColor(this->getStart().toDisplayString(), DATE_COLOR);
		date2 = setTabledHTMLColor(this->getEnd().toDisplayString(),DATE_COLOR);
		date1 += date2;
		result += date1;
	} else if (this->taskType == TaskType::DEADLINE) {
	    result += "<td></td>";
		date1 = setTabledHTMLColor(this->getEnd().toDisplayString(),DATE_COLOR);
		result += date1;
	} else {
		result += "<td></td><td></td>";
	}

	string c = "<td style=\"color: "+CATEGORY_COLOR+"\">";
	for (int i=0; i<categories.size(); i++) {
		c += "#";
		c +=categories[i];
		c +=" ";
	}
    result += c;
	result +="</td>";
	return result;

}

// @Author A0105541U
string Task::toFileString() {
	string buffer;
	vector<string>::iterator it;
	if (this->done == true)  {
		buffer +=TASK_DONE;
		buffer += DELIMITER; 
	} else {
		buffer += TASK_NOTDONE; 
		buffer += DELIMITER; 
	}

	buffer += this->taskName.c_str();
	buffer += DELIMITER;
	for(it = categories.begin(); it != categories.end(); it++) {
		string st = *it;
		buffer += "#";
		buffer += st;
		buffer +=DELIMITER;
	}
	
	switch (this->taskType) {
		case FLOATING: 
				break;
		case SCHEDULE:
				buffer += getStart().toString();
				buffer +=DELIMITER;
				buffer += getEnd().toString();
				buffer +=DELIMITER;
				break;
		case DEADLINE:
				buffer += getEnd().toString();
				buffer +=DELIMITER;
	}
	return buffer;
}

